Skip to content

feat(webui): add web user interface - #25

Closed
sounil wants to merge 1 commit into
masterfrom
webui-2
Closed

feat(webui): add web user interface#25
sounil wants to merge 1 commit into
masterfrom
webui-2

Conversation

@sounil

@sounil sounil commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

User friendly web interface for those who don't want to use the CLI. Also makes for nice demos.

Full description of specifications are at WEBUI_SPEC.md

@gadievron

Copy link
Copy Markdown
Collaborator

Thank you for building this, @sounil 🙏 — the web UI is a great addition.

I've rebuilt it on current master (your Python-side plumbing was superseded by later merges) and hardened it through a multi-lens security review, opened as #235 with your authorship preserved via Co-authored-by. This PR (#25) can be closed in favor of it.

The new PR credits you as the original author; happy to adjust the attribution or fold in any feedback you have there.

gadievron added a commit that referenced this pull request Aug 14, 2026
Adds an opt-in, loopback-only web UI (`openant serve`) that drives the existing
scan pipeline from a browser: submit a repo URL or local path, watch scan logs
stream over SSE, and read the HTML report / markdown summary / disclosures.

Original work by Sounil Yu (PR #25, branch knostic/webui-2); this reconciles it
onto current master (taking master on the 5 superseded Python-side conflicts,
keeping only the additive Go/UI surface + a one-line serveCmd registration) and
hardens it. Supersedes #25.

Security posture — the server binds a loopback address (127.0.0.1 by default), EVERY route rejects a
non-loopback Host (DNS-rebinding guard in the securityHeaders middleware, GET
routes included), and every state-changing request additionally requires a
per-instance crypto/rand CSRF token (constant-time compare) plus a same-origin
check. So the read and write surfaces are both reachable only by a caller
already on the local origin — this is defense-in-depth hardening, not a
remotely-exploitable vulnerability. One residual caveat, tracked below: the live
HTML report loads Tailwind/Chart.js from CDNs, so a CDN compromise could run
script in the server origin.

- git-clone SSRF guard (repoHostBlocked): classifies canonical IPs by value
  (loopback/link-local/unspecified/AWS IPv4+IPv6 IMDS/Alibaba blocked; RFC1918 +
  IPv6-ULA allowed for internal servers), resolves DNS names and blocks any
  sensitive result, and blocks ANY non-canonical numeric literal outright
  (encoding-proof for literals: decimal/octal/hex/short-form/mod-2^32-wrap, IPv6
  zone-ids, IPv4-mapped forms). Fails closed on non-ASCII/IDNA hosts, backslash/
  unparseable URLs, and scp multi-userinfo; blocks the .localhost TLD; passes
  git -c http.followRedirects=false. DNS-rebinding of a repo hostname between the
  check and the clone is out of scope (the guard covers literals + one resolution).
- Rejects credentials embedded in an http(s) repo URL (they would be logged
  verbatim); the python subprocess runs with -P so a hostile openant/ package in
  the scanned repo can't shadow the real module.
- DOMPurify 3.4.13 (vendored, no CDN; updated from 3.1.6 = CVE-2025-15599 /
  CVE-2026-41238) with a strict INERT allowlist (no style/form/img/svg — a repo
  can break out of a markdown code fence, so the default allowlist's active
  content is refused) on client-rendered markdown + a strict bluemonday allowlist on
  the report SafeRemediation path; the API-key value is never sent to any page.
- Robustness: managed-writer stderr with WaitDelay (no deadlock on a detached
  child; a WaitDelay-tripped exit-0 scan keeps its output rather than erroring),
  signal-exit treated as error, LogBuf bounded by line count AND bytes, delete
  that waits (up to 5s) for the runner before removing the dir, and a synchronized
  shutdown (drainMu + WaitShutdown) that cancels in-flight scans before exit.
- The job output dir holds files derived from the untrusted repo, so served
  files (report.html, summary, disclosures) are opened O_NOFOLLOW and confirmed to
  be regular files within the job dir (a symlink to a host secret is refused
  atomically at open — no check-then-read race), and the clone
  is time-bounded (15m) so a hostile remote can't hold a scan slot forever.

Tested: host-classification suite (41 blocked forms + a legit allow-set),
auth-gate rejections (rebinding Host, cross-site, foreign Origin, credential URL),
remediation-XSS, job-lifecycle (drain/cancel/recover/delete-waits-for-runner),
the SSE Last-Event-ID overflow clamp, and the stderr WaitDelay mechanism. go
build / go vet / go test ./... green; go test -race clean; semgrep + CodeQL
(CWE-918 SSRF = 0); real end-to-end scans of Python/Go/JS repos through the UI.

Deferred (tracked, not in this PR): vendoring the report-template CDN scripts
(Tailwind is a CDN JIT; needs an asset build step, and the template is shared
with `openant report -f html`) and the CSP/COEP that depends on it; the marked
12.0.2 update (no known CVE; output is DOMPurify-sanitized); the unbounded stdout
capture buffer (openant's stdout is its own bounded JSON envelope).

Process-group kill (Setpgid/SIGKILL) and O_NOFOLLOW are Unix-only, so they live
in procgroup_unix.go / procgroup_other.go build-tagged files; the code builds and
vets clean on linux, darwin, and windows.

Reproduce:
  git fetch origin && git checkout <this-branch>
  cd apps/openant-cli && go build -o /tmp/openant-cli . && /tmp/openant-cli serve --addr 127.0.0.1:8765
  go test ./...

Co-authored-by: Sounil Yu <4305467+sounil@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gadievron added a commit that referenced this pull request Aug 14, 2026
Adds an opt-in, loopback-only web UI (`openant serve`) that drives the existing
scan pipeline from a browser: submit a repo URL or local path, watch scan logs
stream over SSE, and read the HTML report / markdown summary / disclosures.

Original work by Sounil Yu (PR #25, branch knostic/webui-2); this reconciles it
onto current master (taking master on the 5 superseded Python-side conflicts,
keeping only the additive Go/UI surface + a one-line serveCmd registration) and
hardens it. Supersedes #25.

Security posture — the server binds a loopback address (127.0.0.1 by default), EVERY route rejects a
non-loopback Host (DNS-rebinding guard in the securityHeaders middleware, GET
routes included), and every state-changing request additionally requires a
per-instance crypto/rand CSRF token (constant-time compare) plus a same-origin
check. So the read and write surfaces are both reachable only by a caller
already on the local origin — this is defense-in-depth hardening, not a
remotely-exploitable vulnerability. One residual caveat, tracked below: the live
HTML report loads Tailwind/Chart.js from CDNs, so a CDN compromise could run
script in the server origin.

- git-clone SSRF guard (repoHostBlocked): classifies canonical IPs by value
  (loopback/link-local/unspecified/AWS IPv4+IPv6 IMDS/Alibaba blocked; RFC1918 +
  IPv6-ULA allowed for internal servers), resolves DNS names and blocks any
  sensitive result, and blocks ANY non-canonical numeric literal outright
  (encoding-proof for literals: decimal/octal/hex/short-form/mod-2^32-wrap, IPv6
  zone-ids, IPv4-mapped forms). Fails closed on non-ASCII/IDNA hosts, backslash/
  unparseable URLs, and scp multi-userinfo; blocks the .localhost TLD; passes
  git -c http.followRedirects=false. DNS-rebinding of a repo hostname between the
  check and the clone is out of scope (the guard covers literals + one resolution).
- Rejects credentials embedded in an http(s) repo URL (they would be logged
  verbatim); the python subprocess runs with -P so a hostile openant/ package in
  the scanned repo can't shadow the real module.
- DOMPurify 3.4.13 (vendored, no CDN; updated from 3.1.6 = CVE-2025-15599 /
  CVE-2026-41238) with a strict INERT allowlist (no style/form/img/svg — a repo
  can break out of a markdown code fence, so the default allowlist's active
  content is refused) on client-rendered markdown + a strict bluemonday allowlist on
  the report SafeRemediation path; the API-key value is never sent to any page.
- Robustness: managed-writer stderr with WaitDelay (no deadlock on a detached
  child; a WaitDelay-tripped exit-0 scan keeps its output rather than erroring),
  signal-exit treated as error, LogBuf bounded by line count AND bytes, delete
  that waits (up to 5s) for the runner before removing the dir, and a synchronized
  shutdown (drainMu + WaitShutdown) that cancels in-flight scans before exit.
- The job output dir holds files derived from the untrusted repo, so served
  files (report.html, summary, disclosures) are opened O_NOFOLLOW and confirmed to
  be regular files within the job dir (a symlink to a host secret is refused
  atomically at open — no check-then-read race), and the clone
  is time-bounded (15m) so a hostile remote can't hold a scan slot forever.

Tested: host-classification suite (41 blocked forms + a legit allow-set),
auth-gate rejections (rebinding Host, cross-site, foreign Origin, credential URL),
remediation-XSS, job-lifecycle (drain/cancel/recover/delete-waits-for-runner),
the SSE Last-Event-ID overflow clamp, and the stderr WaitDelay mechanism. go
build / go vet / go test ./... green; go test -race clean; semgrep + CodeQL
(CWE-918 SSRF = 0); real end-to-end scans of Python/Go/JS repos through the UI.

Deferred (tracked, not in this PR): vendoring the report-template CDN scripts
(Tailwind is a CDN JIT; needs an asset build step, and the template is shared
with `openant report -f html`) and the CSP/COEP that depends on it; the marked
12.0.2 update (no known CVE; output is DOMPurify-sanitized); the unbounded stdout
capture buffer (openant's stdout is its own bounded JSON envelope).

Process-group kill (Setpgid/SIGKILL) and O_NOFOLLOW are Unix-only, so they live
in procgroup_unix.go / procgroup_other.go build-tagged files; the code builds and
vets clean on linux, darwin, and windows.

Reproduce:
  git fetch origin && git checkout <this-branch>
  cd apps/openant-cli && go build -o /tmp/openant-cli . && /tmp/openant-cli serve --addr 127.0.0.1:8765
  go test ./...

Documents the command in the top-level README (### Web UI under "Analyzing a project").

Co-authored-by: Sounil Yu <4305467+sounil@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gadievron added a commit that referenced this pull request Aug 14, 2026
Adds an opt-in, loopback-only web UI (`openant serve`) that drives the existing
scan pipeline from a browser: submit a repo URL or local path, watch scan logs
stream over SSE, and read the HTML report / markdown summary / disclosures.

Original work by Sounil Yu (PR #25, branch knostic/webui-2); this reconciles it
onto current master (taking master on the 5 superseded Python-side conflicts,
keeping only the additive Go/UI surface + a one-line serveCmd registration) and
hardens it. Supersedes #25.

Security posture — the server binds a loopback address (127.0.0.1 by default), EVERY route rejects a
non-loopback Host (DNS-rebinding guard in the securityHeaders middleware, GET
routes included), and every state-changing request additionally requires a
per-instance crypto/rand CSRF token (constant-time compare) plus a same-origin
check. So the read and write surfaces are both reachable only by a caller
already on the local origin — this is defense-in-depth hardening, not a
remotely-exploitable vulnerability. One residual caveat, tracked below: the live
HTML report loads Tailwind/Chart.js from CDNs, so a CDN compromise could run
script in the server origin.

- git-clone SSRF guard (repoHostBlocked): classifies canonical IPs by value
  (loopback/link-local/unspecified/AWS IPv4+IPv6 IMDS/Alibaba blocked; RFC1918 +
  IPv6-ULA allowed for internal servers), resolves DNS names and blocks any
  sensitive result, and blocks ANY non-canonical numeric literal outright
  (encoding-proof for literals: decimal/octal/hex/short-form/mod-2^32-wrap, IPv6
  zone-ids, IPv4-mapped forms). Fails closed on non-ASCII/IDNA hosts, backslash/
  unparseable URLs, and scp multi-userinfo; blocks the .localhost TLD; passes
  git -c http.followRedirects=false. DNS-rebinding of a repo hostname between the
  check and the clone is out of scope (the guard covers literals + one resolution).
- Rejects credentials embedded in an http(s) repo URL (they would be logged
  verbatim); the python subprocess runs with -P so a hostile openant/ package in
  the scanned repo can't shadow the real module.
- DOMPurify 3.4.13 (vendored, no CDN; updated from 3.1.6 = CVE-2025-15599 /
  CVE-2026-41238) with a strict INERT allowlist (no style/form/img/svg — a repo
  can break out of a markdown code fence, so the default allowlist's active
  content is refused) on client-rendered markdown + a strict bluemonday allowlist on
  the report SafeRemediation path; the API-key value is never sent to any page.
- Robustness: managed-writer stderr with WaitDelay (no deadlock on a detached
  child; a WaitDelay-tripped exit-0 scan keeps its output rather than erroring),
  signal-exit treated as error, LogBuf bounded by line count AND bytes, delete
  that waits (up to 5s) for the runner before removing the dir, and a synchronized
  shutdown (drainMu + WaitShutdown) that cancels in-flight scans before exit.
- The job output dir holds files derived from the untrusted repo, so served
  files (report.html, summary, disclosures) are opened O_NOFOLLOW and confirmed to
  be regular files within the job dir (a symlink to a host secret is refused
  atomically at open — no check-then-read race), and the clone
  is time-bounded (15m) so a hostile remote can't hold a scan slot forever.

Tested: host-classification suite (41 blocked forms + a legit allow-set),
auth-gate rejections (rebinding Host, cross-site, foreign Origin, credential URL),
remediation-XSS, job-lifecycle (drain/cancel/recover/delete-waits-for-runner),
the SSE Last-Event-ID overflow clamp, and the stderr WaitDelay mechanism. go
build / go vet / go test ./... green; go test -race clean; semgrep + CodeQL
(CWE-918 SSRF = 0); real end-to-end scans of Python/Go/JS repos through the UI.

Deferred (tracked, not in this PR): vendoring the report-template CDN scripts
(Tailwind is a CDN JIT; needs an asset build step, and the template is shared
with `openant report -f html`) and the CSP/COEP that depends on it; the marked
12.0.2 update (no known CVE; output is DOMPurify-sanitized); the unbounded stdout
capture buffer (openant's stdout is its own bounded JSON envelope).

Process-group kill (Setpgid/SIGKILL) and O_NOFOLLOW are Unix-only, so they live
in procgroup_unix.go / procgroup_other.go build-tagged files; the code builds and
vets clean on linux, darwin, and windows.

Reproduce:
  git fetch origin && git checkout <this-branch>
  cd apps/openant-cli && go build -o /tmp/openant-cli . && /tmp/openant-cli serve --addr 127.0.0.1:8765
  go test ./...

Documents the command in the top-level README (### Web UI under "Analyzing a project").

Co-authored-by: Sounil Yu <4305467+sounil@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gadievron

Copy link
Copy Markdown
Collaborator

Closing — this shipped as #235, now merged to master, with your authorship preserved (Co-authored-by: Sounil Yu on the merge commit). Thanks again, @sounil! 🙏

@gadievron gadievron closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants